package ajsynchro;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import polyglot.ast.Block;
import polyglot.ast.CanonicalTypeNode;
import polyglot.ast.Catch;
import polyglot.ast.Expr;
import polyglot.ast.Formal;
import polyglot.ast.LocalDecl;
import polyglot.ast.MethodDecl;
import polyglot.ast.Node;
import polyglot.ast.NodeFactory;
import polyglot.ast.Stmt;
import polyglot.ast.Try;
import polyglot.ast.TypeNode;
import polyglot.types.Flags;
import polyglot.types.TypeSystem;
import polyglot.util.Position;
import polyglot.util.TypedList;
import polyglot.ext.jl.ast.Node_c;
import polyglot.ext.jl.parse.Name;

import abc.aspectj.ast.AdviceDecl_c;
import abc.aspectj.ast.AdviceDecl;
import abc.aspectj.ast.AdviceSpec;
import abc.aspectj.ast.ClassTypeDotId;
import abc.aspectj.ast.FormalPattern;
import abc.aspectj.ast.MethodPattern;
import abc.aspectj.ast.ModifierPattern;
import abc.aspectj.ast.Pointcut;
import abc.aspectj.ast.ProceedCall;
import abc.aspectj.ast.TPEType;
import abc.aspectj.ast.ThrowsPattern;
import abc.aspectj.ast.TypePatternExpr;

import xaj.xc.ast.NodeXAJ;
import xaj.xc.ast.XAJNodeFactory;

//importsymbol ajsynchro.Signal;
//importsymbol ajsynchro.Await;

public class AJSynchroMethod extends AdviceDecl_c implements AdviceDecl, NodeXAJ, xaj.lang.Map {

	/*	
	@grammar extends advice_declaration {
		AJSynchroMethod -> "synchronize" "(" m = method_header ")" 
		                    "with" id = simple_name
		                    "{" ab = [AJBefore] aa = [AJAfter] "}";
	}*/
	
	public static class AJBefore extends Node_c implements NodeXAJ, xaj.lang.Map {
		
		/*@grammar using Signal, Await {
			AJBefore -> "before" ":" b = block_statements;
		}*/
		
		private Map map;
		
		public AJBefore(Position pos) {
			super(pos);
			this.map = new HashMap();
			System.out.println("Criou um objeto AJBefore");
		}
		
		public Object get(String name) {
			return map.get(name);
		}

		public boolean add(String name, Object value) {
			if (!map.containsKey(name)) {
				map.put(name, value);
				return true;
			} else
				return false;
		}

		public Iterator iterator() {
			return map.keySet().iterator();
		}

		public void set(String name, Object value) {
			if (map.containsKey(name)) {
				map.keySet();
				map.put(name, value);
			}
		}
		
		//-------------------------------------------------------------
		
		public Node desugar(NodeFactory nf, TypeSystem ts) {
			List statements = (List) get("b");
			return nf.Block(position(), statements);
		}
	}

	public static class AJAfter extends Node_c implements NodeXAJ, xaj.lang.Map {
		
		/*@grammar using Signal, Await {
			AJAfter -> "after" ":" b = block_statements;
		}*/
		
		private Map map;
		
		public AJAfter(Position pos) {
			super(pos);
			this.map = new HashMap();
			System.out.println("Criou um objeto AJAfter");
		}
		
		public Object get(String name) {
			return map.get(name);
		}

		public boolean add(String name, Object value) {
			if (!map.containsKey(name)) {
				map.put(name, value);
				return true;
			} else
				return false;
		}

		public Iterator iterator() {
			return map.keySet().iterator();
		}

		public void set(String name, Object value) {
			if (map.containsKey(name)) {
				map.keySet();
				map.put(name, value);
			}
		}

		//---------------------------------------------------------------
		
		public Node desugar(NodeFactory nf, TypeSystem ts) {
			List statements = (List) get("b");
			return nf.Block(position(), statements);
		}
	}

	private Map map;
	
	public AJSynchroMethod(Position pos) {
		this(pos, null, null, new TypedList(new LinkedList(),
				TypeNode.class, false), null, null);
	}
	
	private AJSynchroMethod(Position pos, Flags flags, AdviceSpec spec,
			List throwTypes, Pointcut pc, Block body) {
		super(pos, flags, spec, throwTypes, pc, body);
		this.map = new HashMap();
		System.out.println("Criou um objeto AJSynchroMethod");
	}
	
	public Object get(String name) {
		return map.get(name);
	}

	public boolean add(String name, Object value) {
		if (!map.containsKey(name)) {
			map.put(name, value);
			return true;
		} else
			return false;
	}

	public Iterator iterator() {
		return map.keySet().iterator();
	}

	public void set(String name, Object value) {
		if (map.containsKey(name)) {
			map.keySet();
			map.put(name, value);
		}
	}
	
	//--------------------------------------------------------
	
	private static String classHost;
	
	public static String getClassHost() {
		return classHost;
	}
	
	public static void setClassHost(String classHost) {
		AJSynchroMethod.classHost = classHost;
	}
	
	public Node desugar(NodeFactory ft, TypeSystem ts) {
		XAJNodeFactory nf = (XAJNodeFactory) ft;
		//Get parameters
		MethodDecl methodhead = (MethodDecl) get("m");
		Name name = (Name) get("id");
		AJBefore ab = (AJBefore) get("ab");
		AJAfter aa = (AJAfter) get("aa");
		
		/**
		 * Cria o pointcut que captura a execução do método à ser sincronizado
		 */
				
		/**
		 * Cria um modifier pattern referente ao modificador do método
		 * que será sincronizado
		 */
		List modifierlist = new TypedList(new LinkedList(),
				ModifierPattern.class, false);
		modifierlist.add(nf.ModifierPattern(null, methodhead.flags(), true));
		
		/**
		 * Cria um type pattern referente ao tipo de retorno do método
		 * que será sincronizado
		 */
		TypePatternExpr typepattern;
		TypeNode type = methodhead.returnType();
		if(type instanceof CanonicalTypeNode) {
			typepattern = nf.TPEType(position(),type);
		} else {			
			typepattern = nf.TPERefTypePat(position(), nf.RTPName(position(),
					nf.SimpleNamePattern(position(), type.name())));
		}
		
		/**
		 * Cria o class pattern para especificar o método que será sicronizado.
		 * Sempre o mesmo nome definido na entidade Syncronizer que setará o
		 * atributo classHost + nome do método sincronizado. 
		 */		
		ClassTypeDotId classtype = nf.ClassTypeDotId(position(),
				nf.CPEName(position(), nf.SimpleNamePattern(position(), classHost)),
				nf.SimpleNamePattern(position(), methodhead.name()));
			
		/**
		 * Patterns para a lista de parâmetros do método
		 */
		List listformalpattern = new TypedList(new LinkedList(), FormalPattern.class,
                 false);
		List listformal = methodhead.formals();
		Formal f;
		for(int i = 0; i < listformal.size(); ++i) {
			f = (Formal) listformal.get(i);
			TypeNode tn = f.type();
			TypePatternExpr tpe;
			//Verifica se é um tipo primitivo
			if(tn instanceof CanonicalTypeNode) {
				tpe = nf.TPEType(position(), tn);
			} else {
				tpe = nf.TPERefTypePat(position(), nf.RTPName(position(), 
						nf.SimpleNamePattern(position(), tn.name())));
			}			
			listformalpattern.add(nf.TypeFormalPattern(position(), tpe));
		}
			
		/**
		 * Cria o throws pattern para a lista de exceções lançadas pelo método que 
		 * será sincronizado
		 */
		List throwslist = new TypedList(new LinkedList(), ThrowsPattern.class, false);
		List methodthrows = methodhead.throwTypes();
		TypeNode t;
		for(int i = 0; i < methodthrows.size(); ++i) {
			t = (TypeNode) methodthrows.get(i);
			throwslist.add(nf.ThrowsPattern(position(), nf.CPEName(position(),
					nf.SimpleNamePattern(position(), t.name())), true));
		}
		
		/**
		 * Cria um method pattern referente ao método a ser sincronizado com os
		 * objetos construido anteriormente
		 */
		MethodPattern method = nf.MethodPattern(position(), modifierlist,
				typepattern, classtype, listformalpattern, throwslist);	
		
		/**
		 * Constroi o pointcut execution, usando o MethodPattern que pega
		 * o método a ser sincronizado e alguns argumentos que deve pegar. 
		 */
		//Por enquanto não coloquei nenhum args.
		Pointcut pointc = nf.PCExecution(position(), method);
		
		/**
		 * Constroi o corpo do advice 
		 */
		//Trava o objeto com lock		
		List listStm = new TypedList(new LinkedList(), Stmt.class, false);		
		Stmt methodLock = nf.Eval(position(), nf.Call(position(), name.toExpr(),
				"lock"));
		listStm.add(methodLock);
		
		//Adiciona todos os comantos de bloco before, caso exista
		if(ab != null) {
			Block b = (Block) ab.desugar(ft, ts);
			listStm.addAll(b.statements());
		}
		//Chama o método proceed. Ainda sem parâmetros
		ProceedCall proceedCall = nf.ProceedCall(position(),
				nf.This(position()),
				new TypedList(new LinkedList(), Expr.class, false));
		//Cria uma variável para armazenar o valor retornado por proceed.
		//Se não for void
		if(!methodhead.returnType().name().equals("void")) {
			LocalDecl local = nf.LocalDecl(position(), Flags.NONE, methodhead.returnType(),
				"aux_aux", proceedCall);
			listStm.add(local);
			if(aa != null) {
				Block b = (Block) aa.desugar(ft, ts);
				listStm.addAll(b.statements());
			}
			listStm.add(nf.Return(position(), nf.AmbExpr(position(), "aux_aux")));
		} else {
			listStm.add(nf.Eval(position(), proceedCall));
			if(aa != null) {
				Block b = (Block) aa.desugar(ft, ts);
				listStm.addAll(b.statements());
			}
			listStm.add(nf.Return(position(), null));
		}		
		
		Block tryBlock = nf.Block(position(), listStm);
		Stmt stm = nf.Eval(position(), nf.Call(position(), name.toExpr(), "unlock"));
		Block finallyBlock = nf.Block(position(), stm);
		Try trystm = nf.Try(position(), tryBlock,
				new TypedList(new LinkedList(), Catch.class, false),
				finallyBlock);
		
		/**
		 * Cria uma declaração de around, cujo tipo de retorno
		 * e os paramâmetros são os mesmos do método a ser sincronizado
		 */
		AdviceSpec spec = nf.Around(position(), methodhead.returnType(),
				//Eu acredito que o around deve ter os mesmos parametros 
				//que o método a ser sincronizado. Por enquanto está sem nenhum
				//methodhead.formals());
				new TypedList(new LinkedList(), Formal.class, false));
		
		/**
		 * Cria a declaração do around advice completa, que inclui:
		 * - Modificadores do advice. Sempre sem modificadores
		 * - AdviceSpec. Aroudn advice criado anteriormente
		 * - Lista de exceções lançadas. As mesma do método a ser sincronizado
		 * - Pointcut. Ponto junção que captura o método a ser sincronizado
		 * - Bloco. Block do advice
		 */
		AdviceDecl advice = nf.AdviceDecl(position(), Flags.NONE, spec, 
				methodhead.throwTypes(), pointc,
				nf.Block(position(), trystm));
		
		return advice;
	}
}
